﻿[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")]
{%- if UseSystemTextJson -%}
internal class JsonInheritanceConverterAttribute : JsonConverterAttribute
{
    public string DiscriminatorName { get; }

    public JsonInheritanceConverterAttribute(System.Type baseType, string discriminatorName = "discriminator")
        : base(typeof(JsonInheritanceConverter<>).MakeGenericType(baseType))
    {
        DiscriminatorName = discriminatorName;
    }
}

internal class JsonInheritanceConverter<TBase> : System.Text.Json.Serialization.JsonConverter<TBase>
{
    private readonly string _discriminatorName;

    public JsonInheritanceConverter()
    {
        var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute<JsonInheritanceConverterAttribute>(typeof(TBase));
        _discriminatorName = attribute?.DiscriminatorName ?? "discriminator";
    }

    public JsonInheritanceConverter(string discriminatorName)
    {
        _discriminatorName = discriminatorName;
    }

    public virtual string DiscriminatorName => _discriminatorName;

    public override TBase Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
    {
        var document = System.Text.Json.JsonDocument.ParseValue(ref reader);
        var hasDiscriminator = document.RootElement.TryGetProperty(_discriminatorName, out var discriminator);
        var subtype = GetDiscriminatorType(document.RootElement, typeToConvert, hasDiscriminator ? discriminator.GetString() : null);

        var bufferWriter = new System.IO.MemoryStream();
        using (var writer = new System.Text.Json.Utf8JsonWriter(bufferWriter))
        {
            document.RootElement.WriteTo(writer);
        }

        return (TBase)System.Text.Json.JsonSerializer.Deserialize(bufferWriter.ToArray(), subtype, options);
    }

    public override void Write(System.Text.Json.Utf8JsonWriter writer, TBase value, System.Text.Json.JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString(_discriminatorName, GetDiscriminatorValue(value.GetType()));

        var bytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes((object)value, options);
        var document = System.Text.Json.JsonDocument.Parse(bytes);
        foreach (var property in document.RootElement.EnumerateObject())
        {
            property.WriteTo(writer);
        }

        writer.WriteEndObject();
    }

    public virtual string GetDiscriminatorValue(System.Type type)
    {
        var jsonInheritanceAttributeDiscriminator = GetSubtypeDiscriminator(type);
        if (jsonInheritanceAttributeDiscriminator != null)
        {
            return jsonInheritanceAttributeDiscriminator;
        }

        return type.Name;
    }

    protected virtual System.Type GetDiscriminatorType(System.Text.Json.JsonElement jObject, System.Type objectType, string discriminatorValue)
    {
        var jsonInheritanceAttributeSubtype = GetObjectSubtype(objectType, discriminatorValue);
        if (jsonInheritanceAttributeSubtype != null)
        {
            return jsonInheritanceAttributeSubtype;
        }

        if (objectType.Name == discriminatorValue)
        {
            return objectType;
        }

        var typeName = objectType.Namespace + "." + discriminatorValue;
        var subtype = System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType).Assembly.GetType(typeName);
        if (subtype != null)
        {
            return subtype;
        }

        throw new System.InvalidOperationException("Could not find subtype of '" + objectType.Name + "' with discriminator '" + discriminatorValue + "'.");
    }

    private System.Type GetObjectSubtype(System.Type objectType, string discriminator)
    {
        foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), true))
        {
            if (attribute.Key == discriminator)
                return attribute.Type;
        }

        return objectType;
    }

    private string GetSubtypeDiscriminator(System.Type objectType)
    {
        foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), true))
        {
            if (attribute.Type == objectType)
                return attribute.Key;
        }

        return objectType.Name;
    }
}
{%- else -%}
internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter
{
    internal static readonly string DefaultDiscriminatorName = "discriminator";

    private readonly string _discriminator;

    [System.ThreadStatic]
    private static bool _isReading;

    [System.ThreadStatic]
    private static bool _isWriting;

    public JsonInheritanceConverter()
    {
        _discriminator = DefaultDiscriminatorName;
    }

    public JsonInheritanceConverter(string discriminator)
    {
        _discriminator = discriminator;
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        try
        {
            _isWriting = true;

            var jObject = Newtonsoft.Json.Linq.JObject.FromObject(value, serializer);
            jObject.AddFirst(new Newtonsoft.Json.Linq.JProperty(_discriminator, GetSubtypeDiscriminator(value.GetType())));
            writer.WriteToken(jObject.CreateReader());
        }
        finally
        {
            _isWriting = false;
        }
    }

    public override bool CanWrite
    {
        get
        {
            if (_isWriting)
            {
                _isWriting = false;
                return false;
            }
            return true;
        }
    }

    public override bool CanRead
    {
        get
        {
            if (_isReading)
            {
                _isReading = false;
                return false;
            }
            return true;
        }
    }

    public override bool CanConvert(System.Type objectType)
    {
        return true;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var jObject = serializer.Deserialize<Newtonsoft.Json.Linq.JObject>(reader);
        if (jObject == null)
            return null;

        var discriminatorValue = jObject.GetValue(_discriminator);
        var discriminator = discriminatorValue != null ? Newtonsoft.Json.Linq.Extensions.Value<string>(discriminatorValue) : null;
        var subtype = GetObjectSubtype(objectType, discriminator);

        var objectContract = serializer.ContractResolver.ResolveContract(subtype) as Newtonsoft.Json.Serialization.JsonObjectContract;
        if (objectContract == null || System.Linq.Enumerable.All(objectContract.Properties, p => p.PropertyName != _discriminator))
        {
            jObject.Remove(_discriminator);
        }

        try
        {
            _isReading = true;
            return serializer.Deserialize(jObject.CreateReader(), subtype);
        }
        finally
        {
            _isReading = false;
        }
    }

    private System.Type GetObjectSubtype(System.Type objectType, string discriminator)
    {
        foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), true))
        {
            if (attribute.Key == discriminator)
                return attribute.Type;
        }

        return objectType;
    }

    private string GetSubtypeDiscriminator(System.Type objectType)
    {
        foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), true))
        {
            if (attribute.Type == objectType)
                return attribute.Key;
        }

        return objectType.Name;
    }
}
{%- endif -%}